# encoding: UTF-8
require 'citrus'
require 'date'

CURRENCIES = {
    'ALL' => 'Lek' ,
    'USD' => '$'   ,
    'AFN' => '؋'   ,
    'ARS' => '$'   ,
    'AWG' => 'ƒ'   ,
    'AUD' => '$'   ,
    'AZN' => 'ман' ,
    'BSD' => '$'   ,
    'BBD' => '$'   ,
    'BYR' => 'p.'  ,
    'EUR' => '€'   ,
    'BZD' => 'BZ$' ,
    'BMD' => '$'   ,
    'BOB' => '$b'  ,
    'BAM' => 'KM'  ,
    'BGN' => 'лв'  ,
    'BRL' => 'R$'  ,
    'GBP' => '£'   ,
    'BND' => '$'   ,
    'KHR' => '៛'   ,
    'CAD' => '$'   ,
    'KYD' => '$'   ,
    'CLP' => '$'   ,
    'CNY' => '¥'   ,
    'COP' => '$'   ,
    'CRC' => '₡'   ,
    'HRK' => 'kn'  ,
    'CUP' => '₱'   ,
    'CZK' => 'Kč'  ,
    'DKK' => 'kr'  ,
    'DOP' => 'RD$' ,
    'XCD' => '$'   ,
    'EGP' => '£'   ,
    'SVC' => '$'   ,
    'GBP' => '£'   ,
    'FKP' => '£'   ,
    'FJD' => '$'   ,
    'GHC' => '¢'   ,
    'GIP' => '£'   ,
    'GTQ' => 'Q'   ,
    'GGP' => '£'   ,
    'GYD' => '$'   ,
    'HKD' => '$'   ,
    'HUF' => 'Ft'  ,
    'ISK' => 'kr'  ,
    'INR' => 'INR' ,
    'IDR' => 'Rp'  ,
    'IRR' => '﷼'   ,
    'IMP' => '£'   ,
    'ILS' => '₪'   ,
    'JMD' => 'J$'  ,
    'JPY' => '¥'   ,
    'JEP' => '£'   ,
    'KZT' => 'лв'  ,
    'KPW' => '₩'   ,
    'KRW' => '₩'   ,
    'KGS' => 'лв'  ,
    'LAK' => '₭'   ,
    'LBP' => '£'   ,
    'LKR' => '₨'  ,
    'LVL' => 'Ls'  ,
    'LRD' => '$'   ,
    'CHF' => 'CHF' ,
    'LTL' => 'Lt'  ,
    'HNL' => 'L'   ,
    'MKD' => 'ден' ,
    'MYR' => 'RM'  ,
    'MUR' => '₨'  ,
    'MXN' => '$'   ,
    'MNT' => '₮'   ,
    'MZN' => 'MT'  ,
    'NAD' => '$'   ,
    'NPR' => '₨'  ,
    'ANG' => 'ƒ'   ,
    'NZD' => '$'   ,
    'NIO' => 'C$'  ,
    'NGN' => '₦'   ,
    'KPW' => '₩'   ,
    'NOK' => 'kr'  ,
    'OMR' => '﷼'   ,
    'PKR' => '₨'  ,
    'PAB' => 'B/.' ,
    'PYG' => 'Gs'  ,
    'PEN' => 'S/.' ,
    'PHP' => 'Php' ,
    'PLN' => 'zł'  ,
    'BWP' => 'P'   ,
    'QAR' => '﷼'   ,
    'RON' => 'lei' ,
    'RUB' => 'руб' ,
    'SHP' => '£'   ,
    'SAR' => '﷼'   ,
    'RSD' => 'Дин.',
    'SCR' => '₨'  ,
    'SGD' => '$'   ,
    'SBD' => '$'   ,
    'SOS' => 'S'   ,
    'ZAR' => 'R'   ,
    'KRW' => '₩'   ,
    'SEK' => 'kr'  ,
    'CHF' => 'CHF' ,
    'SRD' => '$'   ,
    'SYP' => '£'   ,
    'TWD' => 'NT$' ,
    'THB' => '฿'   ,
    'TTD' => 'TT$' ,
    'TRY' => 'TL'  ,
    'TRL' => '₤'   ,
    'TVD' => '$'   ,
    'UAH' => '₴'   ,
    'GBP' => '£'   ,
    'UYU' => '$U'  ,
    'UZS' => 'лв'  ,
    'VEF' => 'Bs'  ,
    'VND' => '₫'   ,
    'YER' => '﷼'   ,
    'ZWD' => 'Z$'
}

module TweetGrammar
  THOUSAND_SEPARATORS = ['_', "\\'", ' ']
  FLOAT_POINT         = ['.', ',']

  def self.to_symbols a, n = false
    a.map {|s| "#{n ? '!' : ''}`#{s}`"}.join (' | ')
  end


  str = <<GRM
  grammar Tweet

    ## Hierarchical syntax
    rule messages
      message
    end

    rule message
      mess_transaction | mess_lend | mess_borrow | mess_operation
    end

    rule mess_operation
      date money space comment
    end

    rule mess_transaction
      money space ('->' | '~' | '>') space money space comment
    end

    rule mess_lend
      money space ('->' | 'lend to' | '>') space wallet comment
    end

    rule mess_borrow
      wallet space ('->' | 'borrow from' | '>') space money space comment
    end

    rule wallet
      ('@' name:[a-z]+)?
    end

    rule money
      wallet space currency space number space currency space wallet
    end

    rule comment
      .+
    end

    rule date
      ([0-9]*4 '-' [0-9]*2 '-' [0-9]*2)?
    end

    rule number
      ('+' | '-')? [1-9]1*3 (thousand_separators | [0-9])* (float_point [0-9]+)? ![0-9]
    end

    rule thousand_separators
      #{to_symbols(THOUSAND_SEPARATORS)}
    end

    rule float_point
      #{to_symbols(FLOAT_POINT)}
    end

    rule currency
      (#{to_symbols(CURRENCIES.flatten) })?
    end

    rule space
      [ \t]*
    end

  end
GRM

#  puts str
  Citrus.eval(str)

  def self.process_wallet(match)
    res = match && (match[0][:name][0] || match[1][:name][0] || nil)
    res.to_s unless res.nil?
  end

  def self.process_money match
    {
        :value => process_value(match),
        :currency => process_currency(match),
        :wallet => process_wallet(match[:wallet])
    }
  end

  def self.extract_date comment
    res = comment.scan(/\d{4}-\d{2}-\d{2}/)
    res.empty? ? "" : res[0]
  end

  def self.process_operation match
    res = process_money(match[:money][0])
    res[:comment] = process_comment(match)

    res[:date] = match[:date][0].to_s
    res[:date] = res[:comment][:date] if res[:date].empty?
    res[:date] = nil if res[:date].empty?
    res[:date] = DateTime.parse(res[:date].to_s).to_i unless res[:date].nil?

    res[:wallet] ||= res[:comment][:wallet]

    res
  end

  def self.process_value match
    # step 1 - get string
    value = match[:number][0].to_s

    # step 2 - convert to Float
    Float(value.split(/[#{THOUSAND_SEPARATORS}]/).join "")
  end

  def self.process_currency match
    # step 1 - get string
    value = match[:currency][0].to_s
    value = match[:currency][1].to_s if value.empty?
    return nil if value.empty?

    value.upcase!
    return value if CURRENCIES.has_key?(value)

    # step 2 - convert to CODE
    res = CURRENCIES.find { |k, v| value == v }
    res && res[0]
  end

  def self.process_comment match
    value = match[:comment][0].to_s
    {
        :text => value,
        :tags => get_tags(value),
        :wallet => get_wallets(value),
        :date => extract_date(value)
    }
  end

  def self.get_tags text
    text.scan(/#([^ \t\n\r]+)/u).flatten
  end

  def self.get_wallets text
    res = text.scan(/@([^ \t\n\r]+)/u).flatten
    res.empty? ? nil : res[0]
  end

  #
  # for each line return hash like
  #    { value: Float
  #      currency: String | nil
  #      wallet: String | nil
  #      comment: {
  #        text: String
  #        tags: [String]
  #        wallets: [String]
  #      }
  #    }
  #
  #
  def self.parse_tweet text
    text.lines.map {|l| process_operation(Tweet.parse(l))}
  end

  def self.parse_money text
    process_money(Tweet.parse(text, :root => :money))
  end
end